<template>
  <span>
    <slot :export-excel="exportExcel" />
    <a ref="downloadRef"></a>
  </span>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import * as XLSX from 'xlsx'
import { cloneDeep, keyBy, pick } from 'lodash-es'
import FileSaver from 'file-saver'

interface Props {
  col: any
  data: any[]
}

const props = withDefaults(defineProps<Props>(), {
  col: () => ({}),
  data: () => []
})

const downloadRef = ref<HTMLAnchorElement>()

function toAB(s: string) {
  const buf = new ArrayBuffer(s.length)
  const view = new Uint8Array(buf)
  for (let i = 0; i !== s.length; ++i) {
    view[i] = s.charCodeAt(i) & 0xff
  }
  return buf
}

//将指定的自然数转换为26进制表示。映射关系：[0-25] -> [A-Z]。
function getCharCol(n: number) {
  let s = ''
  let m = 0
  while (n > 0) {
    m = (n % 26) + 1
    s = String.fromCharCode(m + 64) + s
    n = (n - m) / 26
  }
  return s
}

function mapper(valueMapCallback: Record<string, Function>, allData?: any[]) {
  const data = cloneDeep(allData ?? props.data)
  const colsKey = keyBy(cloneDeep(props.col), 'key')

  data.forEach((row) => {
    Object.keys(row).forEach((k) => {
      if (valueMapCallback && valueMapCallback[k]) {
        row[k] = valueMapCallback[k](row[k])
      } else if (colsKey[k]?.customRender) {
        row[k] = colsKey[k]?.customRender({ text: row[k], record: row }) ?? ''
      }
    })
  })

  return data
}

function columns(col?: any, data?: any[]) {
  // 导出列的key
  const colsKey = keyBy(col || props.col, 'key')
  // 需要导出的列
  const exportCols = pick(data?.[0] ?? props.data?.[0], Object.keys(colsKey))
  // excel的表头
  return Object.keys(exportCols).reduce<any>((prev, k) => {
    prev[k] = colsKey[k].title
    return prev
  }, {})
}

function exportExcel(downName: string, valueMapCallback: Record<string, Function>, allData?: any[], col?: any) {
  const defaultCols = columns(col, allData)
  // 获取列名
  const keyMap = Object.keys(defaultCols)

  //用来保存转换好的json
  const tmpdata: any[] = []

  ;[defaultCols, ...mapper(valueMapCallback, allData)]
    .map((v: any, i) =>
      keyMap.map((k, j) => ({
        v: v[k] ?? '',
        position: (j > 25 ? getCharCol(j) : String.fromCharCode(65 + j)) + (i + 1)
      }))
    )
    .reduce((prev, next) => prev.concat(next))
    .forEach((v: any) => {
      tmpdata[v.position] = { v: v.v }
    })

  const outputPos = Object.keys(tmpdata) //设置区域，比如表格从A1到D10
  const tmpWB = {
    SheetNames: ['mySheet'], // 保存的表标题
    Sheets: {
      mySheet: Object.assign(
        {},
        tmpdata, //内容
        {
          '!ref': outputPos[0] + ':' + outputPos[outputPos.length - 1] //设置填充区域
        }
      )
    }
  }
  //创建二进制对象写入转换好的字节流
  const tmpDown = new Blob(
    [
      toAB(
        XLSX.write(
          tmpWB,
          { bookType: 'xlsx', bookSST: false, type: 'binary' } //这里的数据是用来定义导出的格式类型
        )
      )
    ],
    {
      type: ''
    }
  )
  FileSaver.saveAs(tmpDown, `${downName}.xlsx`)
}
</script>

<script lang="ts">
export default {
  name: 'ExportExcel'
}
</script>
